/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.snda.lidejia.xml2pojo.parser;

import com.snda.lidejia.xml2pojo.annotation.XMLElement;
import com.snda.lidejia.xml2pojo.exception.XMLParserException;
import com.snda.lidejia.xml2pojo.util.ParserArgumentChecker;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.logging.Level;
import org.dom4j.Element;
import org.dom4j.Node;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author lidejia
 */
public class XMLObjectParser implements XMLParser {

    private static final Logger LOGGER = LoggerFactory.getLogger(XMLObjectParser.class);
    //
    private XMLParserPool pool;

    public XMLObjectParser(XMLParserPool pool) {
        this.pool = pool;
    }

    private void parseFields(Class<?> classType, Object instance, Element element) throws XMLParserException {
        Field[] fields = classType.getDeclaredFields();
        for (Field field : fields) {
            XMLElement annotation = field.getAnnotation(XMLElement.class);
            if (annotation != null) {
                String xPath = annotation.value();
                if (xPath == null || xPath.equals("")) {
                    LOGGER.warn("Missing xPath: {}", field);
                    continue;
                }
                Class<?> lookupType = null;
                Class<?> valueType = null;
                Type fieldType = field.getGenericType();
                if (fieldType instanceof ParameterizedType) {
                    ParameterizedType pType = (ParameterizedType) fieldType;
                    lookupType = (Class<?>) pType.getRawType();
                    valueType = (Class<?>) pType.getActualTypeArguments()[0];
                } else {
                    lookupType = (Class<?>) fieldType;
                    valueType = lookupType;
                }
                //
                XMLParser parser = pool.getParser(lookupType);
                //
                List<Node> nodes = element.selectNodes(xPath);
                Node[] nodeArray = null;
                if (nodes.size() > 0) {
                    if (!(parser instanceof XMLListParser)) {
                        nodeArray = new Node[1];
                        if (nodes.size() != 1) {
                            LOGGER.warn("Multi elements for xPath: {}", xPath);
                        }
                        nodeArray[0] = nodes.get(0);
                    } else {
                        nodeArray = new Node[nodes.size()];
                        nodes.toArray(nodeArray);
                    }
                } else {
                    LOGGER.warn(String.format("Missing elements for xPath '%s' on field '%s' of class '%s'", xPath, field, classType));
                    continue;
                }
                Object fieldValue = parser.parse(valueType, nodeArray);
                if (fieldValue != null) {
                    try {
                        field.setAccessible(true);
                        field.set(instance, fieldValue);
                    } catch (IllegalArgumentException ex) {
                        java.util.logging.Logger.getLogger(XMLObjectParser.class.getName()).log(Level.SEVERE, null, ex);
                    } catch (IllegalAccessException ex) {
                        java.util.logging.Logger.getLogger(XMLObjectParser.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        }
        Class<?> superType = classType.getSuperclass();
        if (superType != null) {
            parseFields(superType, instance, element);
        }

    }

    public Object parse(Class<?> type, Node... nodes) throws XMLParserException {
        //
        ParserArgumentChecker.checkSingle(nodes);
        //
        Object instance = null;
        try {
            instance = type.newInstance();
            Element element = (Element) nodes[0];
            parseFields(type, instance, element);
        } catch (XMLParserException e) {
            throw e;
        } catch (Exception e) {
            LOGGER.error(null, e);
            throw new XMLParserException(e);
        }
        return instance;
    }
}
